package com.yfjin.h264.camera2;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import androidx.annotation.NonNull;

public class Camera2Helper {

    private Context mContext;
    private Size mBestPreviewSize;

    private Handler mCameraHandler;
    private HandlerThread mCameraHandlerThread;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCaptureSession;

    //1路 UI显示
    private TextureView mTextureView;
    //2路 原始数据
    private Handler mDataHandler;
    private HandlerThread mDataHandlerThread;
    private ImageReader mDataImageReader;

    public Camera2Helper(Context context) {
        mContext = context.getApplicationContext();
    }

    private boolean isStarting =false;

    public boolean isStarting() {
        return isStarting;
    }

    //开启摄像头
    @SuppressLint("MissingPermission")
    public synchronized Size start(TextureView textureView) {
        isStarting =true;
        mTextureView = textureView;
        //摄像头的管理类
        CameraManager cameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraList=cameraManager.getCameraIdList();
            String cameraId=cameraList[cameraList.length-1];
            //这个摄像头的配置信息
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
            //以及获取图片输出的尺寸和预览画面输出的尺寸、支持哪些格式
            //获取到的、像预览尺寸
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            //寻找一个 最合适的尺寸
            mBestPreviewSize = Camera2Util.getBestSupportedSize(null, new ArrayList<>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));

            //不单独HandlerThread的话，默认是主线程
            mCameraHandlerThread = new HandlerThread("CameraBackground");
            mCameraHandlerThread.start();
            mCameraHandler = new Handler(mCameraHandlerThread.getLooper());

            initUpload();

            cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice cameraDevice) {
                    mCameraDevice = cameraDevice;
                    //建立绘画
                    createCameraSession();
                }

                @Override
                public void onDisconnected(@NonNull CameraDevice cameraDevice) {
                    cameraDevice.close();
                    mCameraDevice = null;
                }

                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    camera.close();
                    mCameraDevice = null;
                }
            }, mCameraHandler);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return mBestPreviewSize;
    }


    private void initUpload() {
        mDataImageReader = ImageReader.newInstance(
                mBestPreviewSize.getWidth(), mBestPreviewSize.getHeight(),
                ImageFormat.YUV_420_888, 2
        );
        mDataImageReader.setOnImageAvailableListener(new OnUploadImageAvailable(), mDataHandler);
        mDataHandlerThread = new HandlerThread("UploadNV21Background");
        mDataHandlerThread.start();
        mDataHandler = new Handler(mDataHandlerThread.getLooper());
    }


    private void createCameraSession() {
        try {
            CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            List<Surface> surfaceList = createSurfaceList(requestBuilder);
            //建立 链接     目的  几路 数据出口
            mCameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                    // 系统的相机
                    // The camera is already closed
                    if (null == mCameraDevice) {
                        return;
                    }
                    try {
                        cameraCaptureSession.setRepeatingRequest(requestBuilder.build()
                                , new CameraCaptureSession.CaptureCallback() {}
                                , mCameraHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                    mCaptureSession = cameraCaptureSession;
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }


    private List<Surface> createSurfaceList(CaptureRequest.Builder requestBuilder) {
        List<Surface> surfaceList = new ArrayList<>();
        if (mTextureView != null) {
            //预览的TexetureView
            SurfaceTexture texture = mTextureView.getSurfaceTexture();
            //设置预览宽高
            texture.setDefaultBufferSize(mBestPreviewSize.getWidth(), mBestPreviewSize.getHeight());
            //创建有一个Surface 画面1
            Surface surface = new Surface(texture);
            requestBuilder.addTarget(surface);
            surfaceList.add(surface);
        }
        if (mDataImageReader !=null){
            requestBuilder.addTarget(mDataImageReader.getSurface());
            surfaceList.add(mDataImageReader.getSurface());
        }

        return surfaceList;
    }


    public void stop() {
        mYUVCallback=null;
        mNV21Callback=null;
        mCaptureSession.close();
        mCameraDevice.close();
        mCameraHandlerThread.quitSafely();
        mCameraHandler.removeCallbacksAndMessages(null);

        if (mDataImageReader !=null){
            mDataImageReader.close();
            mDataImageReader =null;
        }
        if (mDataHandlerThread !=null){
            mDataHandlerThread.quitSafely();
            mDataHandlerThread =null;
        }
        if (mDataHandler !=null){
            mDataHandler.removeCallbacksAndMessages(null);
            mDataHandler =null;
        }
        isStarting=false;

    }


    private class OnUploadImageAvailable implements ImageReader.OnImageAvailableListener {

        private byte[] y;
        private byte[] u;
        private byte[] v;

        private byte[] nv21;

        @Override
        public void onImageAvailable(ImageReader reader) {
            Image image = reader.acquireLatestImage();
            if (image==null){
                return;
            }
            Image.Plane[] planes = image.getPlanes();
            if (y == null) {
                y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
                u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
                v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
            }
            if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
                planes[0].getBuffer().get(y);
                planes[1].getBuffer().get(u);
                planes[2].getBuffer().get(v);
            }
            int stride=planes[0].getRowStride();

            if (mYUVCallback != null) {
                mYUVCallback.onPreview(y, u, v, mBestPreviewSize.getWidth(),mBestPreviewSize.getHeight(), stride);
            }

            if (mNV21Callback != null) {
//                if (nv21==null){
//                    nv21=new byte[y.length*3/2];
//                }
                mNV21Callback.onPreview(YUV_420_888toNV21(image), mBestPreviewSize.getWidth(),mBestPreviewSize.getHeight(), stride);
            }

            image.close();
        }
    }

    private IYUVCallback mYUVCallback;
    private INV21Callback mNV21Callback;

    public void setYUVDataCallback(IYUVCallback callback) {
        mYUVCallback=callback;
    }

    public void setNV21DataCallback(INV21Callback callback) {
        mNV21Callback=callback;
    }

    public interface IYUVCallback {
        void onPreview(byte[] y, byte[] u, byte[] v, int width ,int height, int stride);
    }

    public interface INV21Callback {
        void onPreview(byte[] nv21,int width ,int height, int stride);
    }




    public static byte[] YUV_420_888toNV21(Image image) {
        Rect crop = image.getCropRect();
        int format = image.getFormat();
        int width = crop.width();
        int height = crop.height();
        Image.Plane[] planes = image.getPlanes();
        byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
        byte[] rowData = new byte[planes[0].getRowStride()];

        int channelOffset = 0;
        int outputStride = 1;
        for (int i = 0; i < planes.length; i++) {
            switch (i) {
                case 0:
                    channelOffset = 0;
                    outputStride = 1;
                    break;
                case 1:
                    channelOffset = width * height + 1;
                    outputStride = 2;
                    break;
                case 2:
                    channelOffset = width * height;
                    outputStride = 2;
                    break;
            }

            ByteBuffer buffer = planes[i].getBuffer();
            int rowStride = planes[i].getRowStride();
            int pixelStride = planes[i].getPixelStride();

            int shift = (i == 0) ? 0 : 1;
            int w = width >> shift;
            int h = height >> shift;
            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
            for (int row = 0; row < h; row++) {
                int length;
                if (pixelStride == 1 && outputStride == 1) {
                    length = w;
                    buffer.get(data, channelOffset, length);
                    channelOffset += length;
                } else {
                    length = (w - 1) * pixelStride + 1;
                    buffer.get(rowData, 0, length);
                    for (int col = 0; col < w; col++) {
                        data[channelOffset] = rowData[col * pixelStride];
                        channelOffset += outputStride;
                    }
                }
                if (row < h - 1) {
                    buffer.position(buffer.position() + rowStride - length);
                }
            }
        }
        return data;
    }
}
